/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <cstddef>
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <memory>
#include <napi/native_common.h>
#include <napi/native_api.h>
#include <string.h>
#include <cstdio>
#include <cstdlib>
#include <string>
#include "securec.h"
#include "hilog_wrapper.h"
#include "scancode_utils.h"
#include "ScanTHos.h"

namespace SwanLink {
namespace Finance {

static napi_value Open(napi_env env, napi_callback_info info)
{
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "Enter");

    size_t requireArgc = 2;
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    napi_value thisArg = nullptr;

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
    NAPI_ASSERT(env, argc == requireArgc, "Wrong number of arguments");

    napi_valuetype valuetype0;
    NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));

    napi_valuetype valuetype1;
    NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));

    if (valuetype0 != napi_string || valuetype1 != napi_number) {
        napi_throw_type_error(env, nullptr, "Wrong arguments, type mismatch.");
        return nullptr;
    }

    char name[256] = {0};
    size_t len = 0;
    DEBUG_NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], name, 256, &len));

    int rate;
    NAPI_CALL(env, napi_get_value_int32(env, args[1], &rate));

    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "uart_init, name = %{public}s, rate = %{public}d", name, rate);

    int fd = Open_Device(name, rate);
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "uart_init, fd = %{public}d", fd);

    if (fd == -1) {
        SCANCODE_HILOGE(SCANCODE_JS_NAPI, "uart_init error");
    }

    napi_value result;
    DEBUG_NAPI_CALL(env, napi_create_int32(env, fd, &result));
    return result;
}

static napi_value Close(napi_env env, napi_callback_info info)
{
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "Enter");

    size_t requireArgc = 1;
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args , nullptr, nullptr));
    NAPI_ASSERT(env, argc == requireArgc, "Wrong number of arguments");

    napi_valuetype valuetype0;
    NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));

    if (valuetype0 != napi_number) {
        napi_throw_type_error(env, nullptr, "Wrong arguments, type mismatch.");
        return nullptr;
    }

    int fd;
    NAPI_CALL(env, napi_get_value_int32(env, args[0], &fd));
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "close_uart, fd = %{public}d", fd);

    int ret = Close_Device(fd);
    if (ret == -1) {
        SCANCODE_HILOGE(SCANCODE_JS_NAPI, "close_uart error\n");
    }
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "close_uart\n");

    napi_value result;
    DEBUG_NAPI_CALL(env, napi_create_int32(env, ret, &result));
    return result;
}

static napi_value StartScan(napi_env env, napi_callback_info info)
{
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "Enter");

    size_t requireArgc = 3;
    size_t argc = 3;
    napi_value args[3] = {nullptr};
    napi_value thisArg = nullptr;

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
    NAPI_ASSERT(env, argc == requireArgc, "Wrong number of arguments");

    napi_valuetype valuetype0;
    NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));

    napi_valuetype valuetype1;
    NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));

    napi_valuetype valuetype2;
    NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));

    if (valuetype0 != napi_number || valuetype1 != napi_number || valuetype2 != napi_number) {
        napi_throw_type_error(env, nullptr, "Wrong arguments, type mismatch.");
        return nullptr;
    }

    struct ScanAsyncContext : public AsyncContext {
        int fd;
        int maxLen;
        int timeout;
        unsigned char rvdata[255];
    };

    ScanAsyncContext *asyncContext = new ScanAsyncContext;

    NAPI_CALL(env, napi_get_value_int32(env, args[0], &asyncContext->fd));
    NAPI_CALL(env, napi_get_value_int32(env, args[1], &asyncContext->maxLen));
    NAPI_CALL(env, napi_get_value_int32(env, args[2], &asyncContext->timeout));

    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "open_ScanT, fd = %{public}d, maxLen = %{public}d, timeout = %{public}d",
                    asyncContext->fd, asyncContext->maxLen, asyncContext->timeout);

    napi_value promise = nullptr;
    NAPI_CALL(env, napi_create_promise(env, &asyncContext->deferred, &promise));

    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "ScanAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);

    napi_status status = napi_create_async_work(env, nullptr, resourceName,
        [](napi_env env, void *data) {
            SCANCODE_HILOGE(SCANCODE_JS_NAPI, "ScanExecuteCB start");

            ScanAsyncContext *context = static_cast<ScanAsyncContext*>(data);

            int len = open_ScanT(context->fd, context->rvdata, context->maxLen, context->timeout);
            SCANCODE_HILOGE(SCANCODE_JS_NAPI, "open_ScanT len = %{public}d\n", len);

            if (len <= 0) {
                return;
            }

            char tmp[255*3] = {0};
            for (int i = 0; i < len; i++) {
                sprintf_s(&tmp[3*i], (255-i)*3, "%2X ", context->rvdata[i]);
            }
            SCANCODE_HILOGE(SCANCODE_JS_NAPI, "open_ScanT data = %{public}s\n", tmp);

            void *arrayData = nullptr;
            napi_value arrayBuffer = nullptr;
            size_t bufferSize = len;

            NAPI_CALL_RETURN_VOID(env, napi_create_arraybuffer(env, len, &arrayData, &arrayBuffer));

            if (memcpy_s(arrayData, bufferSize, reinterpret_cast<const void*>(context->rvdata), bufferSize) != 0) {
                SCANCODE_HILOGE(SCANCODE_JS_NAPI, "failed to copy buffer");
                return;
            }

	        NAPI_CALL_RETURN_VOID(env, napi_create_typedarray(env, napi_uint8_array, len, arrayBuffer, 0, &context->data));

            context->status = true;
            SCANCODE_HILOGE(SCANCODE_JS_NAPI, "ScanExecuteCB end");
        },
        ScancodeNapiUtils::CompleteCallback, static_cast<void*>(asyncContext), &asyncContext->work);

    if (status != napi_ok) {
        SCANCODE_HILOGE(SCANCODE_JS_NAPI, "Failed to create napi_create_async_work for StartScan, status = %{public}d", status);

        napi_value result = nullptr;
        napi_get_undefined(env, &result);
        return result;
    }

    napi_queue_async_work(env, asyncContext->work);
    return promise;
}

static napi_value StopScan(napi_env env, napi_callback_info info)
{
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "Enter");

    size_t requireArgc = 1;
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args , nullptr, nullptr));
    NAPI_ASSERT(env, argc == requireArgc, "Wrong number of arguments");

    napi_valuetype valuetype0;
    NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));

    if (valuetype0 != napi_number) {
        napi_throw_type_error(env, nullptr, "Wrong arguments, type mismatch.");
        return nullptr;
    }

    int fd;
    NAPI_CALL(env, napi_get_value_int32(env, args[0], &fd));
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "close_ScanT, fd = %{public}d", fd);

    int ret = close_ScanT(fd);
    if (ret == -1) {
        SCANCODE_HILOGE(SCANCODE_JS_NAPI, "close_ScanT error");
    }
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "close_ScanT");

    napi_value result;
    DEBUG_NAPI_CALL(env, napi_create_int32(env, ret, &result));
    return result;
}

EXTERN_C_START
/* 
 * function for module exports
 */
static napi_value ScancodeInit(napi_env env, napi_value exports)
{
    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "Enter");

    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("Open", Open),
        DECLARE_NAPI_FUNCTION("Close", Close),
        DECLARE_NAPI_FUNCTION("StartScan", StartScan),
        DECLARE_NAPI_FUNCTION("StopScan", StopScan)
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);

    SCANCODE_HILOGE(SCANCODE_JS_NAPI, "End");

    return exports;
}
EXTERN_C_END

/*
 * Module definition
 */
static napi_module g_module = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = ScancodeInit,
    .nm_modname = "scancode",
    .nm_priv = (reinterpret_cast<void*>(0)),
    .reserved = {0}
};
/*
 * Module registration
 */
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
    napi_module_register(&g_module);
}

}  // namespace Finance
}  // namespace SwanLink